
#import <Foundation/Foundation.h>

#import "KCBaseDefine.h"

/**
 * This is the main assert macro that you should use. Asserts should be compiled out
 * in production builds. You can customize the assert behaviour by setting a custom
 * assert handler through `KCSetAssertFunction`.
 */
#ifndef NS_BLOCK_ASSERTIONS
#define KCAssert(condition, ...) do { \
  if ((condition) == 0) { \
    _KCAssertFormat(#condition, __FILE__, __LINE__, __func__, __VA_ARGS__); \
    if (KC_NSASSERT) { \
      [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
        file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; \
    } \
  } \
} while (false)
#else
#define KCAssert(condition, ...) do {} while (false)
#endif
KC_EXTERN void _KCAssertFormat(
  const char *, const char *, int, const char *, NSString *, ...
) NS_FORMAT_FUNCTION(5,6);

/**
 * Report a fatal condition when executing. These calls will _NOT_ be compiled out
 * in production, and crash the app by default. You can customize the fatal behaviour
 * by setting a custom fatal handler through `KCSetFatalHandler`.
 */
KC_EXTERN void KCFatal(NSError *error);

/**
 * The default error domain to be used for React errors.
 */
KC_EXTERN NSString *const KCErrorDomain;

/**
 * JS Stack trace provided as part of an NSError's userInfo
 */
KC_EXTERN NSString *const KCJSStackTraceKey;

/**
 * Name of fatal exceptions generated by KCFatal
 */
KC_EXTERN NSString *const KCFatalExceptionName;

/**
 * A block signature to be used for custom assertion handling.
 */
typedef void (^KCAssertFunction)(
                                  NSString *condition,
                                  NSString *fileName,
                                  NSNumber *lineNumber,
                                  NSString *function,
                                  NSString *message
                                  );

typedef void (^KCFatalHandler)(NSError *error);

/**
 * Convenience macro for asserting that a parameter is non-nil/non-zero.
 */
#define KCAssertParam(name) KCAssert(name, @"'%s' is a required parameter", #name)

/**
 * Convenience macro for asserting that we're running on main thread.
 */
#define KCAssertMainThread() KCAssert([NSThread isMainThread], \
  @"This function must be called on the main thread")

/**
 * Convenience macro for asserting that we're running off the main thread.
 */
#define KCAssertNotMainThread() KCAssert(![NSThread isMainThread], \
@"This function must not be called on the main thread")

/**
 * These methods get and set the current assert function called by the KCAssert
 * macros. You can use these to replace the standard behavior with custom assert
 * functionality.
 */
KC_EXTERN void KCSetAssertFunction(KCAssertFunction assertFunction);
KC_EXTERN KCAssertFunction KCGetAssertFunction(void);

/**
 * This appends additional code to the existing assert function, without
 * replacing the existing functionality. Useful if you just want to forward
 * assert info to an extra service without changing the default behavior.
 */
KC_EXTERN void KCAddAssertFunction(KCAssertFunction assertFunction);

/**
 * This method temporarily overrides the assert function while performing the
 * specified block. This is useful for testing purposes (to detect if a given
 * function asserts something) or to suppress or override assertions temporarily.
 */
KC_EXTERN void KCPerformBlockWithAssertFunction(void (^block)(void), KCAssertFunction assertFunction);

/**
 These methods get and set the current fatal handler called by the KCFatal method.
 */
KC_EXTERN void KCSetFatalHandler(KCFatalHandler fatalHandler);
KC_EXTERN KCFatalHandler KCGetFatalHandler(void);

/**
 * Get the current thread's name (or the current queue, if in debug mode)
 */
KC_EXTERN NSString *KCCurrentThreadName(void);

/**
 * Helper to get generate exception message from NSError
 */
KC_EXTERN NSString *KCFormatError(NSString *message, NSArray<NSDictionary<NSString *, id> *> *stacktrace, NSUInteger maxMessageLength);

/**
 * Convenience macro to assert which thread is currently running (DEBUG mode only)
 */
#if DEBUG

#define KCAssertThread(thread, format...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
KCAssert( \
  [(id)thread isKindOfClass:[NSString class]] ? \
    [KCCurrentThreadName() isEqualToString:(NSString *)thread] : \
    [(id)thread isKindOfClass:[NSThread class]] ? \
      [NSThread currentThread] ==  (NSThread *)thread : \
      dispatch_get_current_queue() == (dispatch_queue_t)thread, \
  format); \
_Pragma("clang diagnostic pop")

#else

#define KCAssertThread(thread, format...) do { } while (0)

#endif
